package couk.cleverthinking.javamoney;

import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.sql.Types;

import org.hibernate.HibernateException;
import org.hibernate.usertype.*;
import org.postgresql.util.PGobject;

/**
 * A Hibernate custom User Type to deal with moneytype in the database.
 * 
 * @author rsmith
 * 
 */
public class HibernateMoneyUserType implements UserType {
	private static final int SQL_TYPE = Types.STRUCT;
	/**
	 * Database object type we are mapping to {@link Money} class.
	 */
	public static final String DB_OBJECT_TYPE = "moneytype";

	public int[] sqlTypes() {
		return new int[] { SQL_TYPE };
	}

	public Class returnedClass() {
		return Money.class;
	}

	/**
	 * Determines equality between 2 types. I'm not sure what sort of object
	 * Hibernate will pass, ie. is it a Money object or a string? * This routine
	 * deals with either
	 * 
	 * (non-Javadoc)
	 * 
	 * @see org.hibernate.usertype.UserType#equals(java.lang.Object,
	 *      java.lang.Object)
	 */
	public boolean equals(Object o1, Object o2) throws HibernateException {
		// true if the objects are 2 instances of the same object
		if (o1 == o2) {
			return true;
		}
		// false if either is null
		if (o1 == null || o2 == null) {
			return false;
		}

		l("o1 = " + o1 + ", o2=" + o2 + ", type="
				+ o1.getClass().getCanonicalName());
		// do a String comparison if we were called with strings
		if (o1.getClass().getCanonicalName().equals("java.lang.String")) {
			return (((String) o1).equals(o2));
		}

		// convert Mney to Strings and call ourselves to check again
		final Money user1 = (Money) o1;
		final Money user2 = (Money) o2;
		// equal if string representations match
		return equals(user1.getSqlValue(), user2.getSqlValue());
	}

	// private boolean equals(final String str1, final String str2) {
	// return StringUtils.equals(str1, str2);
	// }

	public void l(String s) {
		try {
//			System.out.println("###HMUT######### " + s);
		} catch (Exception e) {
			System.out
					.println("log was called with something that caused the following exception... processing continued");
			e.printStackTrace();
		}
	}

	public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
			throws HibernateException, SQLException {
		assert names.length == 1;
		l("in nullsafe GET with " + resultSet.getObject(names[0]));
		// final Struct struct = (Struct) resultSet.getObject(names[0]);
		if (resultSet.wasNull()) {
			return null;
		}
		final Money money = new Money(resultSet.getObject(names[0]).toString());

		// user.setCreatedBy((String) struct.getAttributes()[0]);
		// user.setCreateDate((Date) struct.getAttributes()[1]);
		// user.setUpdatedBy((String) struct.getAttributes()[2]);
		// user.setUpdateDate((Date) struct.getAttributes()[3]);
		return money;
	}

	public void nullSafeSet(PreparedStatement statement, Object value, int index)
			throws HibernateException, SQLException {
		// this sets the parameter to null. Consider that it might be better to
		// set it to ZERO
		if (value == null) {
			statement.setObject(index, null);
			return;
		}
		l("entering nullsafe SET with value=" + value + " , type="
				+ value.getClass().getCanonicalName());
		PGobject pgo = new PGobject();
		l("dbtype = "
				+ statement.getParameterMetaData().getParameterType(index));
		// deal with the fact that that database type could be either numeric or moneytype 
		if (statement.getParameterMetaData().getParameterType(index) == java.sql.Types.NUMERIC) {
			statement.setBigDecimal(index, ((Money) value).getBase());
		} else {
			pgo.setType(DB_OBJECT_TYPE);
			l("sqlValue="+((Money) value).getSqlValue());
			pgo.setValue(((Money) value).getSqlValue());
			statement.setObject(index, pgo);
		}
	}

	public Object deepCopy(Object value) throws HibernateException {
		if (value == null) {
			return null;
		}
		l("value=" + value);
		final Money clone = new Money(((Money) value).getSqlValue());
		return clone;
	}

	public boolean isMutable() {
		return true;
	}

	@Override
	public Object assemble(Serializable cached, Object owner)
			throws HibernateException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Serializable disassemble(Object value) throws HibernateException {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public int hashCode(Object x) throws HibernateException {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public Object replace(Object original, Object target, Object owner)
			throws HibernateException {
		// TODO Auto-generated method stub
		return null;
	}

}
